package under.wsl.controle;

/**
 * @author alexandre.araujo
 */

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import under.wsl.cacheador.Cache;
import under.wsl.decode.JsonDecodeI;
import under.wsl.request.RequestService;
import under.wsl.service.Service;
import under.wsl.servico.Servico;
import under.wsl.servico.ServicoDAO;


public class Controlador {
	
    private static Controlador instancia = null;  
  
    private Cache cache = null;  
    private String packageServices = "";
    private StringBuffer erros = null;
    private HashMap<Integer,RequestService> requisicoes=null;   
    private JsonDecodeI jsondecode = null;
    private ServicoDAO servicos=null;
    
    
    
    public JsonDecodeI getJsondecode() {
    	if(this.jsondecode==null){
    		try {
				this.jsondecode = (JsonDecodeI) Class.forName("under.wsl.decode.DefaultJsonDecode").newInstance();
			} catch (Exception e) {
				System.out.println("couldt get the default class to decode json");
			}
    	}
		return jsondecode;
	}
	public void setJsondecode(JsonDecodeI jsondecode) {
		this.jsondecode = jsondecode;
	}
	private Controlador(){
        requisicoes = new HashMap<Integer, RequestService>();        
        this.cache = new Cache();
        this.erros = new StringBuffer("");
    }   
    public static Controlador getInstancia() {
        if (instancia == null) {
            instancia = new Controlador();
        }
        return instancia;
    }
    
    public ServicoDAO getServicos() {
        return servicos;
    }

    public void setServicos(ServicoDAO servicos) {
        this.servicos = servicos;
    }
    
    
    public RequestService  getRequisicao(int idRequest){       
        RequestService zero = this.requisicoes.get(idRequest);
        if(zero!=null){
            try{
                this.moveErrosToRequest(zero);
            }catch(Exception e){
                zero.addErro("erro ao transferir erros "+e.getMessage().toString());
            }
            return zero; 
        }       
        zero = new RequestService();
        zero.setIdRequest(idRequest);
        this.moveErrosToRequest(zero);
        zero.addErro("houve erro ao utilizar o request");
        return zero;
    }
    public void addRequestService(RequestService req){
        if(this.requisicoes==null){
           this.requisicoes = new HashMap<Integer,RequestService>();  
        }
        this.requisicoes.put(req.getIdRequest(), req);
    }
    public void removeRequestService(int idRequest){        
        this.requisicoes.remove(idRequest);
    }
    
    public void addErro(String erromsg){
    	System.out.println("erro#"+erromsg);
        this.erros.append(erromsg);
    }
    
    private void moveErrosToRequest(RequestService req){       
       if(!this.erros.toString().equals("")){
            req.addErro(this.erros.toString());
       } 
       this.erros.setLength(0);
    }
  
    public String getResult(int idRequest){ 
    	RequestService reqtmp = this.getRequisicao(idRequest);
    	String retorno = "";
    	if(reqtmp.getCache()){
    		retorno = this.getJsondecode().toJson(this.getRequisicao(idRequest)).replaceFirst("\"rs\":null","\"rs\":"+this.cache.getCache(reqtmp).toString());
    	}else{
    		retorno = this.getJsondecode().toJson(this.getRequisicao(idRequest));
    	}
    	
    	
    	
    	if(this.getRequisicao(idRequest).getFormat().toLowerCase().equals("jsonp")){
    		retorno = this.getRequisicao(idRequest).getCallback()+"("+retorno+")";
    	}
    	/*
    	retorno = retorno.replaceFirst("\"rs\":\"","\"rs\":");
    	
    	if(this.getRequisicao(idRequest).getTipo().equals("list")){
    		retorno = retorno.replaceFirst("(\\})\"","$1");         
    	}else{
    		retorno = retorno.replaceFirst("\",\"tipo\":\"",",\"tipo\":\"");
    	}
    	*/
    	return retorno;
    }
        
    public void deployWS(Map<String, String[]> allParameters,String packageService) {    
    	RequestService req = new RequestService();
        this.packageServices = packageService;
        req.setS(allParameters.get("s")[0]);
        req.setIdRequest(158);
        req.setT(req.getIdRequest());       
        try{         
        	if(allParameters.get("idRequest")!=null){
        		 req.setIdRequest(Integer.parseInt(allParameters.get("idRequest")[0]));
        	}
        	if(allParameters.get("t")!=null){
        		 req.setT(Integer.parseInt(allParameters.get("t")[0]));
        	}else{
        		 req.setT(req.getIdRequest());
        	}          
			if(allParameters.get("format")!=null){
				req.setFormat(allParameters.get("format")[0]);
			}	
			if(allParameters.get("callback")!=null){
				req.setCallback((allParameters.get("callback")[0]));
			}
        }catch(Exception ex) {
        	System.out.println("erro#"+ex.getMessage().toString());
            req.addErro(ex.getMessage().toString()); 
        }  
        
        //req.setTk(Integer.parseInt(allParameters.get("tk")[0]));
        /* para suporte com ajax */ 
        
        
        
        String[] args = null;              
        try {
            if(allParameters.get("p")!=null){
            	args = allParameters.get("p");
            }else if(allParameters.get("p")!=null){
            	args = allParameters.get("p[]");
            }
        } catch (Exception e) {
            req.addErro(e.getMessage().toString());            
        }
        
        
        try {
            Servico servicoE = this.getServicoByName(req);
            if (servicoE == null) {
                req.addErro("servico nao encontrado");                
            }else{               
                this.putServico(servicoE, req, args, allParameters);
            }
        } catch (Exception ex) {
            req.addErro(ex.getMessage().toString());          
        }       
        this.addRequestService(req);
    }
   
    private Servico getServicoByName(RequestService req){
    	Servico serv = new Servico();
    	serv.local = this.packageServices+"."+req.getModule();    	
    	serv.nome = req.getServico();    	
    	return serv;
    }
    
    private Object getInstanciaSubClassePreenchida(String nameFrom, Map<String, String[]> allParameters) {
        Object instanciaC = null;
        try {	
    		String json = this.getJsondecode().mapToJson(allParameters);    	
    		//this.addErro("teste:"+json);
    		json = json.replaceFirst(",\"p\":\\[([^\\]]+)\\]","")
    				.replaceAll(",?\"(t|s|tk|format|callback|idRequest)\":\\[([^\\]]+)\\]","")
    				.replaceFirst(",\"p\":\\[([^\\]]+)\\]","")
    				.replaceAll("\\[|\\]","");   
    		//this.addErro("teste2:"+json);
    		instanciaC = this.getJsondecode().fromJson(json,Class.forName(nameFrom));
    	} catch (Exception e) {
            this.addErro("sub-classe " + nameFrom + " nao foi encontrada:" + e.getMessage().toString());
        }
        return instanciaC;
    }

    
    /**
     * @param servico
     *          servico que se deseja usar
     * @param args
     *          [] argumentos para chamada do metodo
     * @see Esse metodo deve ser usado quando existe argumentos na chamada
     */
    private void putServico(Servico servicoE,RequestService requ, String args[], Map<String, String[]> allParameters) {
       try {
            Class<?> cls =  Class.forName(servicoE.local);
            if (cls == null) {
                requ.addErro("classe nao encontrada");
                return;
            }            
            Method mettmp = null;           
            Object instanciaC = cls.newInstance();
           try{ 
               //tentar pegar metodo rapidamente 
               mettmp = cls.getDeclaredMethod(requ.getMetodo());
           }catch (Exception ex1) {
           	   //requ.addErro("tentativa simples:"+ex1.getMessage().toString());          
           }   
            //inicio intrusao
            
            if(mettmp==null){    
                for (Method m : cls.getDeclaredMethods()) {
                    if (m.getName().equals(requ.getMetodo())&&m.isAnnotationPresent(Service.class)){
                        mettmp = m;
                        break;
                    }
                }
            }
            if(mettmp==null){
                requ.addErro("classe nao tem o metodo " + requ.getMetodo());
                return;
            }
            Service anot = mettmp.getAnnotation(Service.class);    
            if(anot.cache()&&requ.getErro().size()==0){            	
            	if(this.cache.checkExist(requ)){
            		requ.setCache(true);
            	}else{
            		this.getServico(instanciaC, mettmp, requ, args, allParameters);
            		if(requ.getRs()!=null && requ.getErro().size()==0){
            			this.cache.save(requ);	
            		}           		
            	}
            }else{
                this.getServico(instanciaC, mettmp, requ, args, allParameters);
            }            
            if(anot.remove().length>0){
            	this.cache.clear(anot.remove());
            }            
        } catch (Exception e) {
            requ.addErro("erro com argumento: " + e.getMessage().toString());
        }      
    }

    
    
    
    private void getServico(Object instanciaC,Method me2,RequestService requ,String args[], Map<String, String[]> allParameters){
        int paraTM = me2.getParameterTypes().length;        
        if(paraTM==0){
        	this.getServico(instanciaC, me2, requ);
        	return;
        }
        Object argumentos[] = new Object[paraTM];
        Class<?> parametros[] = me2.getParameterTypes();    
        Boolean ok = true;
        for (int z = 0; z < paraTM; z++) {
            if (args == null) {
                argumentos[z] = this.getInstanciaSubClassePreenchida(parametros[z].getCanonicalName(), allParameters);
            } else {
                argumentos[z] = this.getJsondecode().getValorConvertido(parametros[z].getCanonicalName(), args[z]);
                if (argumentos[z] == null) {
                    //deve ser tipo personalizado do usuario					
                    argumentos[z] = this.getInstanciaSubClassePreenchida(parametros[z].getCanonicalName(), allParameters);
                }
            }
        }
        if (ok) {
            try {
                requ.setTipo(me2.getReturnType().getSimpleName()); 
                
                if(requ.getTipo().indexOf("list") > -1){
                	requ.setRs(this.getJsondecode().toJson((List<?>) me2.invoke(instanciaC,argumentos)));
                }else if(requ.getTipo().matches(".*(string|date)$")){                	
                	requ.setRs(this.getJsondecode().valueToString(me2.invoke(instanciaC,argumentos), requ.getTipo()));
                }else if(requ.getTipo().matches(".*(int|float|double|bigdecimal|interger|boolean|char)$")){                	
                	requ.setRs(me2.invoke(instanciaC,argumentos));
                }else{
                	requ.setRs(this.getJsondecode().toJson(me2.invoke(instanciaC,argumentos)));
                }                
            } catch (Exception e){
                requ.addErro("erro com parametros:"+e.getMessage().toString());
            }                 
        } else {
            requ.addErro("quantidade de argumentos insuficiente");             
        }   
    }
    
    /**
     * @param servico
     *            servico que se deseja usar
     * @return retorna o metodo executado
     * @see Esse metodo deve ser usado quando nao existe argumentos na chamada
     */
    private void getServico(Object instanciaC,Method me2,RequestService requ){       
        try {
            requ.setTipo(me2.getReturnType().getSimpleName());           
            if(requ.getTipo().indexOf("list") > -1){
            	requ.setRs(this.getJsondecode().toJson((List<?>) me2.invoke(instanciaC)));
            }else if(requ.getTipo().matches(".*(string|date)$")){                	
            	requ.setRs(this.getJsondecode().valueToString(me2.invoke(instanciaC), requ.getTipo()));
            }else if(requ.getTipo().matches(".*(int|float|double|bigdecimal|interger|boolean|char)$")){                	
            	requ.setRs(me2.invoke(instanciaC));
            }else{
            	requ.setRs(this.getJsondecode().toJson(me2.invoke(instanciaC)));
            }
        } catch (Exception e){
            requ.addErro(e.getMessage().toString());
        }        
    }
}